﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Jtype1
{
    class CommandParser
    {
        //new command object which will be returned
        private Command command;

        //to store all the chopped components of user command line
        private List<string> commands;

        //original user command line
        private string userCommand;
        
        //string lists for possible prepositions and keywords inputs
        private List<string> locationSet = new List<string> { "AT", "@" };
        private List<string> deadlineSet = new List<string> { "BEFORE", "<", "ON"};
        private List<string> addSet = new List<string> { "ADD", ".A", "+" };
        private List<string> deleteSet = new List<string> { "DELETE", "DEL", ".D", "-" };
        private List<string> searchSet = new List<string> { "SEARCH", ".S", "?" };
        private List<string> editSet = new List<string> { "EDIT", ".E"};
        private List<string> clearSet = new List<string> { "CLEAR", "CLR", "--" };
        private List<string> homeSet = new List<string> { "HOME", ".H" };

        //string lists for possible day of week conventional inputs
        private List<string> tomorrowSet = new List<string> { "TOMORROW", "TMRW", "TOM", "TMR" };
        private List<string> sundaySet = new List<string> { "SUN", "SUNDAY" };
        private List<string> mondaySet = new List<string> { "MON", "MONDAY" };
        private List<string> tuesdaySet = new List<string> { "TUE", "TUESDAY" };
        private List<string> wednesdaySet = new List<string> { "WED", "WEDNESDAY" };
        private List<string> thursdaySet = new List<string> { "THU", "THURSDAY" };
        private List<string> fridaySet = new List<string> { "FRI", "FRIDAY" };
        private List<string> saturdaySet = new List<string> { "SAT", "SATURDAY" };

        //string lists for possible month inputs
        private List<string> janSet = new List<string> { "JAN", "JANUARY" };
        private List<string> febSet = new List<string> { "FEB", "FEBRUARY" };
        private List<string> marSet = new List<string> { "MAR", "MARCH" };
        private List<string> aprSet = new List<string> { "APR", "APRIL"};
        private List<string> maySet = new List<string> { "MAY" };
        private List<string> junSet = new List<string> { "JUN", "JUNE" };
        private List<string> julSet = new List<string> { "JUL", "JULY" };
        private List<string> augSet = new List<string> { "AUG", "AUGUST" };
        private List<string> sepSet = new List<string> { "SEP", "SEPTEMBER" };
        private List<string> octSet = new List<string> { "OCT", "OCTOTBER" };
        private List<string> novSet = new List<string> { "NOV", "NOVEMBER" };
        private List<string> decSet = new List<string> { "DEC", "DECEMBER" };

        /// <summary>
        /// constructor for CommandParser class
        /// split the userCommand to a list
        /// create a command object
        /// </summary>
        /// <param name="_userCommand">command user input</param>

        public CommandParser(string _userCommand)
        {
            userCommand = _userCommand;
            commands=userCommand.Split(' ').ToList();
            command = new Command();
        }

        
        /// <summary>
        /// main function to analyse the command type and choose which way of command
        /// intepretation it should proceed to 
        /// </summary>
        private void AnalyseCommand()
        {
            string commandType = commands[0].ToUpper();

            if(addSet.Contains(commandType))
                ParseAddCommand();

            if(deleteSet.Contains(commandType))
                ParseDeleteCommand();

            if(searchSet.Contains(commandType))
                ParseSearchCommand();

            if(editSet.Contains(commandType))
                ParseEditCommand();

            if (clearSet.Contains(commandType))
                ParseClearCommand();

            if (homeSet.Contains(commandType))
                ParseHomeCommand();

            if (commandType.ToUpper() == "DONE")
                ParseDoneCommand();
        }

        
        /// <summary>
        /// main function for parsing add commands
        /// will detect the presence of location and deadline 
        /// keywords and prepositions and proceed to calling 
        /// other functions to further parse the command
        /// </summary>
        private void ParseAddCommand()
        {
            int deadlineIndex = -1;
            int locationIndex = -1;
            
            command.Type = "add";

            commands.RemoveAt(0);

            for (int i = 0; i < commands.Count; ++i)
            {
                if (deadlineSet.Contains(commands[i].ToUpper()))
                    deadlineIndex = i;
                if (locationSet.Contains(commands[i].ToUpper()))
                    locationIndex = i;
            }

            if (deadlineIndex == -1 && locationIndex == -1)
            {
                command.TaskDeadline = DateTime.MinValue;
                command.TaskLocation = null;
                command.TaskName = string.Join(" ",commands.ToArray());
            }

            if (deadlineIndex != -1 && locationIndex == -1)
            {
                command.TaskName = string.Join(" ",commands.GetRange(0, deadlineIndex).ToArray());
                SetCommandDeadline(deadlineIndex);
            }
            if (deadlineIndex == -1 && locationIndex != -1)
            {
                command.TaskName = string.Join(" ", commands.GetRange(0, locationIndex).ToArray());
                SetCommandLocation(locationIndex);
            }
            if (deadlineIndex != -1 && locationIndex != -1)
            {
                if( locationIndex < deadlineIndex )
                    command.TaskName = string.Join(" ", commands.GetRange(0, locationIndex).ToArray());
                else
                    command.TaskName = string.Join(" ", commands.GetRange(0, deadlineIndex).ToArray());
                SetCommandLocationAndDeadline(locationIndex, deadlineIndex);
            }
        }

        
        /// <summary>
        /// helper function to get command information
        /// if user only inputs deadline and no location
        /// </summary>
        /// <param name="deadlineIndex">the index of a instance in deadline set in command</param>
        private void SetCommandDeadline (int deadlineIndex)
        {
            
            List<string> userDeadline = commands.GetRange(deadlineIndex+1, commands.Count - deadlineIndex - 1);
            SetDateTime(userDeadline);
        }

        
        /// <summary>
        /// helper function to get command information
        /// if user only inputs location and no deadline
        /// </summary>
        /// <param name="locationIndex">the index of a instance in location set in command</param>
        private void SetCommandLocation(int locationIndex)
        {     
            command.TaskLocation = string.Join(" ",commands.GetRange(locationIndex + 1, commands.Count - 1 - locationIndex).ToArray());
        }

        
        /// <summary>
        /// helper function to get command information
        /// if user inputs both location and deadline
        /// </summary>
        /// <param name="locationIndex">the index of a instance in location set in command</param>
        /// <param name="deadlineIndex">the index of a instance in deadline set in command</param>
        private void SetCommandLocationAndDeadline(int locationIndex, int deadlineIndex)
        {
            if (locationIndex < deadlineIndex)
            {
                
                command.TaskLocation = string.Join(" ",commands.GetRange(locationIndex + 1, deadlineIndex - 1 - locationIndex).ToArray());
                List<string> userDeadline = commands.GetRange(deadlineIndex + 1, commands.Count - deadlineIndex - 1);
                SetDateTime(userDeadline);
            }
            else
            {
  
                command.TaskLocation = string.Join(" ",commands.GetRange(locationIndex + 1, commands.Count - locationIndex - 1).ToArray());
                List<string> userDeadline = commands.GetRange(deadlineIndex + 1, locationIndex - 1 - deadlineIndex);
                SetDateTime(userDeadline);
            }
        }

        //*********************************************************************************************
        //*****************************needs refactoring badly*************************************
        //*********************************************************************************************
        /// <summary>
        /// helper function to set deadline by interpreting conventional date keywords
        /// </summary>
        /// <param name="userDeadline">the list of datetime string</param>
        private void SetDateTime(List<string> userDeadline)
        {
            if (userDeadline.Count == 1)
            {
                if (userDeadline[0].ToUpper() == "TODAY")
                    command.TaskDeadline = DateTime.Today;

                if (tomorrowSet.Contains(userDeadline[0].ToUpper()))
                    command.TaskDeadline = DateTime.Today.AddDays(1);
                // for now we just deal with date but not specific time

                if (sundaySet.Contains(userDeadline[0].ToUpper()))
                    SetDayOfWeek(DayOfWeek.Sunday);

                if (mondaySet.Contains(userDeadline[0].ToUpper()))
                    SetDayOfWeek(DayOfWeek.Monday);

                if (tuesdaySet.Contains(userDeadline[0].ToUpper()))
                    SetDayOfWeek(DayOfWeek.Tuesday);

                if (wednesdaySet.Contains(userDeadline[0].ToUpper()))
                    SetDayOfWeek(DayOfWeek.Wednesday);

                if (thursdaySet.Contains(userDeadline[0].ToUpper()))
                    SetDayOfWeek(DayOfWeek.Thursday);

                if (fridaySet.Contains(userDeadline[0].ToUpper()))
                    SetDayOfWeek(DayOfWeek.Friday);

                if (saturdaySet.Contains(userDeadline[0].ToUpper()))
                    SetDayOfWeek(DayOfWeek.Saturday);

                if(userDeadline[0].ToString().Contains("/"))
                {
                    string modifiedDeadline = userDeadline[0];
                    if(countChar(userDeadline[0], '/') == 1)
                        modifiedDeadline += "/"+DateTime.Today.Year.ToString();
                    
                    command.TaskDeadline = Convert.ToDateTime(modifiedDeadline);     
                }
     
            }

            //if the user input is in the format of "1 July" or "July 1"
            if (userDeadline.Count == 2)
            {
                int dayOfMonth;
                string monthString = string.Empty;

                if (int.TryParse(userDeadline[0], out dayOfMonth))
                {
                    if (janSet.Contains(userDeadline[1].ToUpper()))
                        monthString = "Jan ";

                    if (febSet.Contains(userDeadline[1].ToUpper()))
                        monthString = "Feb ";

                    if (marSet.Contains(userDeadline[1].ToUpper()))
                        monthString = "Mar ";

                    if (aprSet.Contains(userDeadline[1].ToUpper()))
                        monthString = "Apr ";

                    if (maySet.Contains(userDeadline[1].ToUpper()))
                        monthString = "May ";

                    if (junSet.Contains(userDeadline[1].ToUpper()))
                        monthString = "Jun ";

                    if (julSet.Contains(userDeadline[1].ToUpper()))
                        monthString = "Jul ";

                    if (augSet.Contains(userDeadline[1].ToUpper()))
                        monthString = "Aug ";

                    if (sepSet.Contains(userDeadline[1].ToUpper()))
                        monthString = "Sep ";

                    if (octSet.Contains(userDeadline[1].ToUpper()))
                        monthString = "Oct ";

                    if (novSet.Contains(userDeadline[1].ToUpper()))
                        monthString = "Nov ";

                    if (decSet.Contains(userDeadline[1].ToUpper()))
                        monthString = "Dec ";
                }
                else
                {
                    dayOfMonth = int.Parse(userDeadline[1].ToUpper());

                    if (janSet.Contains(userDeadline[0].ToUpper()))
                        monthString = "Jan ";

                    if (febSet.Contains(userDeadline[0].ToUpper()))
                        monthString = "Feb ";

                    if (marSet.Contains(userDeadline[0].ToUpper()))
                        monthString = "Mar ";

                    if (aprSet.Contains(userDeadline[0].ToUpper()))
                        monthString = "Apr ";

                    if (maySet.Contains(userDeadline[0].ToUpper()))
                        monthString = "May ";

                    if (junSet.Contains(userDeadline[0].ToUpper()))
                        monthString = "Jun ";

                    if (julSet.Contains(userDeadline[0].ToUpper()))
                        monthString = "Jul ";

                    if (augSet.Contains(userDeadline[0].ToUpper()))
                        monthString = "Aug ";

                    if (sepSet.Contains(userDeadline[0].ToUpper()))
                        monthString = "Sep ";

                    if (octSet.Contains(userDeadline[0].ToUpper()))
                        monthString = "Oct ";

                    if (novSet.Contains(userDeadline[0].ToUpper()))
                        monthString = "Nov ";

                    if (decSet.Contains(userDeadline[0].ToUpper()))
                        monthString = "Dec ";
                }

                string deadlineString = monthString + dayOfMonth.ToString() + " " + DateTime.Today.Year.ToString();
                command.TaskDeadline = Convert.ToDateTime(deadlineString);
            } 
            
        }

        
        /// <summary>
        /// helper function to count the presence of certain 
        /// character within one string
        /// </summary>
        /// <param name="input">the string need to </param>
        /// <param name="target">target char to be counted in the string</param>
        /// <returns>return the count of </returns>
        private int countChar(string input, char target)
        {
            int count = 0;
            
            foreach (char c in input)
            {
                if (c == target)
                    count++;
            }

            return count;
        }

        /// <summary>
        /// helper function to find the next monday/tuesday/etc.
        /// </summary>
        /// <param name="weekday">weekday like mon,tue...</param>
        private void SetDayOfWeek(DayOfWeek weekday)
        {
            DateTime deadline = DateTime.Today;

            while (deadline.DayOfWeek != weekday)
                deadline=deadline.AddDays(1);

            command.TaskDeadline = deadline;
        }
        
     
        /// <summary>
        /// main function for parsing delete command
        /// set command type to delete and regards the rest as taskindex or taskkey
        /// </summary>
        private void ParseDeleteCommand()
        {
            int index;

            command.Type = "delete";

            commands.RemoveAt(0);
            for (int i = 0; i < commands.Count; i++)
            {
                if (int.TryParse(commands[i], out index))
                    command.TaskIndex.Add(index-1);
                else
                    command.TaskKey.Add(commands[i]);
            }
               
        }

        /// <summary>
        /// set command type to search and the rest of the command will be regarded as taskkey
        /// </summary>
        private void ParseSearchCommand()
        {
            command.Type = "search";

            commands.RemoveAt(0);

            command.TaskKey = commands;
        }

        /// <summary>
        /// main function for pasing edit command
        /// first use search method to find the task and change its location or deadline
        /// </summary>
        private void ParseEditCommand()
        {
            int index;
            int locationIndex = -1;
            int deadlineIndex = -1;
            command.Type ="edit";

            commands.RemoveAt(0);

            if (commands.Count == 0)
                return;

            for (int i = 0; i < commands.Count; ++i)
            {
                if (deadlineSet.Contains(commands[i].ToUpper()))
                    deadlineIndex = i;
                if (locationSet.Contains(commands[i].ToUpper()))
                    locationIndex = i;
            }

            if (int.TryParse(commands[0], out index))
            {
                command.TaskIndex.Add(index - 1);
                if (deadlineIndex == -1 && locationIndex == -1)
                {
                    command.TaskDeadline = DateTime.MinValue;
                    command.TaskLocation = null;

                }

                if (deadlineIndex != -1 && locationIndex == -1)
                    SetCommandDeadline(deadlineIndex);

                if (deadlineIndex == -1 && locationIndex != -1)
                    SetCommandLocation(locationIndex);

                if (deadlineIndex != -1 && locationIndex != -1)
                    SetCommandLocationAndDeadline(locationIndex, deadlineIndex);
            }
            else
            {
                for (int j = 0; j < commands.Count && !(deadlineSet.Contains(commands[j].ToUpper())) && !(locationSet.Contains(commands[j].ToUpper()));j++ )
                    command.TaskKey.Add(commands[j]);
            }
        }
        /// <summary>
        /// set the command type to clear
        /// </summary>
        private void ParseClearCommand()
        {
            command.Type = "clear";
        }
        /// <summary>
        /// set the command type to home
        /// </summary>
        private void ParseHomeCommand()
        {
            command.Type = "home";
        }
        /// <summary>
        /// set the command type to done
        /// </summary>
        private void ParseDoneCommand()
        {
            int index;

            command.Type = "done";

            commands.RemoveAt(0);
            for (int i = 0; i < commands.Count; i++)
            {
                if (int.TryParse(commands[i], out index))
                    command.TaskIndex.Add(index - 1);
                else
                    command.TaskKey.Add(commands[i]);
            }
        }

        
        /// <summary>
        /// public function to return the fully analysed command
        /// back to CommandControl
        /// </summary>
        /// <returns>return the command which commandpaser dealed with</returns>
        public Command GetCommand()
        {
            AnalyseCommand();
            return command;
        }
    }
}
